package com.mofum.scope.spring.web;

import com.mofum.scope.annotation.controller.ControllerScope;
import com.mofum.scope.annotation.controller.IgnoreScope;
import com.mofum.scope.annotation.controller.QueryScope;
import com.mofum.scope.annotation.controller.UpdateScope;
import com.mofum.scope.common.error.AuthenticationException;
import com.mofum.scope.spring.web.constant.Constant;
import com.mofum.scope.annotation.controller.parser.ControllerScopeParser;
import com.mofum.scope.annotation.controller.parser.QueryScopeParser;
import com.mofum.scope.annotation.controller.parser.UpdateScopeParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mofum.scope.common.IScopeAuthenticator;
import com.mofum.scope.common.IScopeConverter;
import com.mofum.scope.common.IScopeExtractor;
import com.mofum.scope.common.annotation.metadata.controller.MControllerScope;
import com.mofum.scope.common.annotation.metadata.controller.MQueryScope;
import com.mofum.scope.common.annotation.metadata.controller.MServiceColumn;
import com.mofum.scope.common.annotation.metadata.controller.MUpdateScope;
import com.mofum.scope.common.model.Parameter;
import com.mofum.scope.common.model.Scope;
import com.mofum.scope.common.model.ServiceObject;
import com.mofum.scope.common.utils.ParentFieldUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 抽象的权限范围切面
 * <p>
 * 提取器负责设置scopeCollection ，设置范围值
 * <p>
 * 转换器 负责转换业务对象或者ID 变成 范围对象或者ID
 * <p>
 * 认证器 负责处理认证该操作是否合法
 *
 * @author yuyang@qxy37.com
 * @since 2019-03-21
 **/
public abstract class AbstractControllerAspectJ extends AbstractAspect {

    @Around("config()")
    public Object process(ProceedingJoinPoint joinPoint) throws Throwable {

        try {
            //Controller 逻辑
            Class<?> clazz = joinPoint.getTarget().getClass();

            Object[] args = joinPoint.getArgs();

            Signature signature = joinPoint.getSignature();

            MethodSignature methodSignature = (MethodSignature) signature;

            Method method = methodSignature.getMethod();

            if (clazz.isAnnotationPresent(IgnoreScope.class)) {
                IgnoreScope annotation = method.getAnnotation(IgnoreScope.class);

                List<String> queryMethods = Arrays.asList(annotation.queryMethods());

                List<String> updateMethods = Arrays.asList(annotation.updateMethods());

                if (queryMethods.size() == 0 && updateMethods.size() == 0) {
                    return joinPoint.proceed(args);
                }

                //反转
                if (annotation.queryReversal()) {

                    if (!queryMethods.contains(method.getName())) {
                        return joinPoint.proceed(args);
                    }

                } else {

                    if (queryMethods.contains(method.getName())) {
                        return joinPoint.proceed(args);
                    }

                }

                //反转
                if (annotation.updateReversal()) {

                    if (!updateMethods.contains(method.getName())) {
                        return joinPoint.proceed(args);
                    }

                } else {

                    if (updateMethods.contains(method.getName())) {
                        return joinPoint.proceed(args);
                    }

                }

            }


            //忽略方法
            if (method.isAnnotationPresent(IgnoreScope.class)) {
                return joinPoint.proceed(args);
            }

            //查询逻辑
            if (method.isAnnotationPresent(QueryScope.class)) {

                QueryScope annotation = method.getAnnotation(QueryScope.class);

                QueryScopeParser queryScopeParser = new QueryScopeParser();

                MQueryScope queryScope = queryScopeParser.parse(annotation);

                List<Scope> scopes = null;

                if (annotation.enableConverter()) {

                    scopes = doConverterProcess(getConvertsClass(clazz, annotation.converts()), joinPoint, queryScope.getColumns(), annotation.type(), annotation.enableCustomServiceColumns());
                }

                if (annotation.enableAuthenticator()) {

                    doAuthenticatorProcess(getAuthenticatorsClass(clazz, annotation.authenticators()), scopes, joinPoint);
                }

                if (annotation.enableExtractor()) {

                    //提取器直接返回结果
                    return doExtractorProcess(getExtractorsClass(clazz, annotation.extractors()), joinPoint);
                }
                return joinPoint.proceed(args);
            }

            //更新逻辑
            if (method.isAnnotationPresent(UpdateScope.class)) {

                UpdateScope annotation = method.getAnnotation(UpdateScope.class);

                UpdateScopeParser updateScopeParser = new UpdateScopeParser();

                MUpdateScope mUpdateScope = updateScopeParser.parse(annotation);

                List<Scope> scopes = null;

                if (annotation.enableConverter()) {
                    scopes = doConverterProcess(getConvertsClass(clazz, annotation.converts()), joinPoint, mUpdateScope.getColumns(), annotation.type(), annotation.enableCustomServiceColumns());
                }

                if (annotation.enableAuthenticator()) {

                    doAuthenticatorProcess(getAuthenticatorsClass(clazz, annotation.authenticators()), scopes, joinPoint);
                }

                if (annotation.enableExtractor()) {
                    //提取器直接返回结果
                    return doExtractorProcess(getExtractorsClass(clazz, annotation.extractors()), joinPoint);
                }
                return joinPoint.proceed(args);
            }


            //最后执行
            if (clazz.isAnnotationPresent(ControllerScope.class)) {

                ControllerScopeParser controllerScopeParser = new ControllerScopeParser();

                MControllerScope mControllerScope = controllerScopeParser.parse(clazz.getAnnotation(ControllerScope.class));

                if (mControllerScope == null) {
                    return joinPoint.proceed(args);
                }

                List<String> ignoreScopes = Arrays.asList(mControllerScope.getIgnoreMethods());

                //忽略部分方法
                if (ignoreScopes.contains(method.getName())) {
                    return joinPoint.proceed(args);
                }

                List<Scope> scopes = null;

                if (mControllerScope.isEnableConverter()) {
                    scopes = doConverterProcess(getConvertsClass(clazz, mControllerScope.getConverters()), joinPoint, mControllerScope.getColumns(), mControllerScope.getType(), mControllerScope.isEnableCustomServiceColumns());
                }

                if (mControllerScope.isEnableAuthenticator()) {
                    doAuthenticatorProcess(getAuthenticatorsClass(clazz, mControllerScope.getAuthenticators()), scopes, joinPoint);
                }

                if (mControllerScope.isEnableExtractor()) {
                    //提取器直接返回结果
                    return doExtractorProcess(getExtractorsClass(clazz, mControllerScope.getExtractors()), joinPoint);
                }
                return joinPoint.proceed(args);
            }
            return joinPoint.proceed(args);
        } catch (Throwable e) {
            getThrowableHandler().handler(e);
        }

        return null;
    }

    /**
     * 获得提取类
     *
     * @param clazz
     * @param classes
     * @return
     */
    private Class<? extends IScopeExtractor>[] getExtractorsClass(Class<?> clazz, Class<? extends IScopeExtractor>[] classes) {
        if (classes == null || classes.length == 0) {
            boolean isScopeExtractor = IScopeExtractor.class.isAssignableFrom(clazz);
            if (isScopeExtractor) {
                classes = new Class[]{clazz};
            }
        }
        return classes;
    }

    /**
     * 获得认证器类
     *
     * @param clazz
     * @param classes
     * @return
     */
    private Class<? extends IScopeConverter>[] getConvertsClass(Class<?> clazz, Class<? extends IScopeConverter>[] classes) {
        if (classes == null || classes.length == 0) {
            boolean isScopeConverter = IScopeConverter.class.isAssignableFrom(clazz);
            if (isScopeConverter) {
                classes = new Class[]{clazz};
            }
        }
        return classes;
    }

    /**
     * 获得认证器类
     *
     * @param clazz
     * @param classes
     * @return
     */
    private Class<? extends IScopeAuthenticator>[] getAuthenticatorsClass(Class<?> clazz, Class<? extends IScopeAuthenticator>[] classes) {
        if (classes == null || classes.length == 0) {
            boolean isScopeAuthenticator = IScopeAuthenticator.class.isAssignableFrom(clazz);
            if (isScopeAuthenticator) {
                classes = new Class[]{clazz};
            }
        }
        return classes;
    }

    /**
     * 做转换操作
     *
     * @param converters
     * @param joinPoint
     * @return
     */
    private List<Scope> doConverterProcess(Class<? extends IScopeConverter>[] converters, ProceedingJoinPoint joinPoint, MServiceColumn[] serviceColumns, String type, boolean custom) throws Exception {

        //Authenticator
        Class<? extends IScopeConverter>[] classes = converters;

        if (classes != null && classes.length > 0) {

            Signature signature = joinPoint.getSignature();
            MethodSignature methodSignature = (MethodSignature) signature;
            String[] names = methodSignature.getParameterNames();

            Class<? extends IScopeConverter> aClass = classes[0];

            IScopeConverter scopeConverter = (IScopeConverter) getWebInstance(aClass);

            Object[] args = joinPoint.getArgs();

            if (scopeConverter == null) {
                return null;
            }


            //自定义生效
            if (custom) {
                Map<String, Object> data = new HashMap<>();
                data.put("arguments", args);
                data.put("type", type);
                //设置范围参数
                return scopeConverter.convert2Scope(data);
            }

            //正常流程如下

            //业务ID集合
            List<ServiceObject> serviceObjects = new ArrayList<>();

            if (serviceColumns == null || serviceColumns.length == 0) {

                MServiceColumn mServiceColumn = new MServiceColumn();

                mServiceColumn.setName(Constant.SCOPE_CONFIG.getServiceColumns());

                mServiceColumn.setType("");

                serviceColumns = new MServiceColumn[]{mServiceColumn};

            }

            List<MServiceColumn> columns = Arrays.asList(serviceColumns);

            //取出column的字段

            for (int i = 0; i < args.length; i++) {

                addServiceObjects(serviceObjects, columns, args[i], names[i], type, true);

            }

            return scopeConverter.convert2Scope(serviceObjects);

        }

        return null;
    }

    /**
     * 添加业务对象
     *
     * @param arg      参 数
     * @param name     参数名
     * @param nextEach
     */
    private void addServiceObjects(List<ServiceObject> serviceObjects, List<MServiceColumn> columns, Object arg, String name, String type, boolean nextEach) throws IOException {
        List<Class> baseClass = Arrays.asList(new Class[]{
                int.class,
                boolean.class,
                byte.class,
                long.class,
                double.class,
                short.class,
                char.class,
                float.class
        });

        if (arg == null) {
            return;
        }
        MServiceColumn serviceColumn = serviceContains(columns, name);

        if (serviceColumn != null) {

            if (serviceColumn.getType() != null) {
                type = serviceColumn.getType();
            }

            if (arg instanceof Number) {
                serviceObjects.add(new ServiceObject(type, arg));
            } else if (arg instanceof Character) {
                serviceObjects.add(new ServiceObject(type, arg));
            } else if (arg instanceof String) {

                if (serviceColumn.isArray()) {

                    if (serviceColumn.isJson()) {
                        ObjectMapper objectMapper = new ObjectMapper();
                        List<? extends Object> list = objectMapper.readValue((String) arg, List.class);
                        for (Object o : list) {
                            serviceObjects.add(new ServiceObject(type, o));
                        }
                    } else {

                        String splitRegex = serviceColumn.getSplitRegex();

                        String[] strings = ((String) arg).split(splitRegex);

                        for (String string : strings) {
                            serviceObjects.add(new ServiceObject(type, string));
                        }
                    }

                } else {

                    if (serviceColumn.isJson()) {
                        ObjectMapper objectMapper = new ObjectMapper();
                        Map<Object, Object> map = objectMapper.readValue((String) arg, Map.class);
                        for (Object o : map.keySet()) {
                            serviceObjects.add(new ServiceObject(type, map.get(o)));
                        }
                    } else {
                        serviceObjects.add(new ServiceObject(type, arg));
                    }
                }

            } else if (arg instanceof Boolean) {
                serviceObjects.add(new ServiceObject(type, arg));
            } else if (baseClass.contains(arg.getClass())) {
                serviceObjects.add(new ServiceObject(type, arg));
            } else if (arg instanceof List) {
                for (Object o : (List) arg) {
                    serviceObjects.add(new ServiceObject(type, o));
                }
            } else if (arg instanceof Map) {
                for (Object o : ((Map) arg).keySet()) {
                    serviceObjects.add(new ServiceObject(type, ((Map) arg).get(o)));
                }
            } else {
                serviceObjects.add(new ServiceObject(type, arg));
            }
        } else {

            if (arg instanceof Object && nextEach && !Object.class.equals(arg.getClass())) {

                if (arg instanceof Map) {
                    Map map = (Map) arg;
                    for (MServiceColumn column : columns) {
                        Object object = map.get(column);
                        addServiceObjects(serviceObjects, columns, object, column.getName(), type, false);
                    }
                }

                if (arg instanceof Object) {

                    for (MServiceColumn column : columns) {

                        if (column.getName() == null || "".equals(column.getName())) {
                            continue;
                        }

                        Field field = ParentFieldUtils.getFieldByName(column.getName(), arg.getClass());

                        if (field == null) {
                            continue;
                        }
                        Object data = null;
                        try {
                            field.setAccessible(true);
                            data = field.get(arg);
                            field.setAccessible(false);
                        } catch (IllegalAccessException e) {
                            data = null;
                        }
                        addServiceObjects(serviceObjects, columns, data, column.getName(), type, false);
                    }

                }

            }

        }
    }

    private MServiceColumn serviceContains(List<MServiceColumn> columns, String name) {
        if (columns == null) {
            return null;
        }

        if (name == null) {
            return null;
        }

        for (MServiceColumn serviceColumn : columns) {

            if (name.equals(serviceColumn.getName())) {
                return serviceColumn;
            }

        }

        return null;

    }

    /**
     * 做认证的操作
     *
     * @param authenticators
     * @param joinPoint
     */
    private void doAuthenticatorProcess(Class<? extends IScopeAuthenticator>[] authenticators, List<Scope> scopes, ProceedingJoinPoint joinPoint) throws Exception {
        //Authenticator
        Class<? extends IScopeAuthenticator>[] classes = authenticators;

        if (classes != null && classes.length > 0) {

            Class<? extends IScopeAuthenticator> aClass = classes[0];

            IScopeAuthenticator iScopeAuthenticator = (IScopeAuthenticator) getWebInstance(aClass);

            if (iScopeAuthenticator == null) {
                return;
            }
            boolean flag = iScopeAuthenticator.testAccess(scopes);

            if (!flag) {
                throw new AuthenticationException("You are not authorized to do this action！");
            }
            //设置范围参数
            return;

        }
        return;
    }

    /**
     * 做提取操作的进程 设置权限范围的值
     *
     * @param extractors
     * @param joinPoint
     * @throws Exception
     */
    private Object doExtractorProcess(Class<? extends IScopeExtractor>[] extractors, ProceedingJoinPoint joinPoint) throws Throwable {
        // Extractor
        Class<? extends IScopeExtractor>[] classes = extractors;

        if (classes != null && classes.length > 0) {

            Signature signature = joinPoint.getSignature();
            MethodSignature methodSignature = (MethodSignature) signature;
            String[] names = methodSignature.getParameterNames();

            Class<? extends IScopeExtractor> aClass = classes[0];

            IScopeExtractor scopeExtractor = (IScopeExtractor) getWebInstance(aClass);

            Object[] args = joinPoint.getArgs();

            if (scopeExtractor == null) {
                return joinPoint.proceed(args);
            }

            List<Scope> scopes = scopeExtractor.extractorScopes(joinPoint.getArgs());
            Object[] newParams = new Object[args.length];
            //设置ScopeCollections
            for (int i = 0; i < args.length; i++) {
                Parameter parameter = new Parameter(i, names[i], args[i]);
                processParameter(parameter, scopes);
                newParams[i] = parameter.getValue();
            }
            try {
                return joinPoint.proceed(newParams);
            } catch (Throwable throwable) {
                throw new RuntimeException(throwable.getMessage(), throwable);
            }
        }
        return null;
    }

    /**
     * 处理参数
     *
     * @param parameter 参数
     * @param scopes    范围
     */
    private void processParameter(Parameter parameter, List<Scope> scopes) {
        if (parameter == null) {
            return;
        }

        Object object = parameter.getValue();

        //为空就不进行后续操作
        if (object == null) {
            //参数 是 权限范围字段
            if (parameter.getName().equals(Constant.SCOPE_SCOPES)) {
                parameter.setValue(scopes);
            }
            return;
        }

        if (!(object instanceof List)) {
            Field field = null;
            field = ParentFieldUtils.getFieldByName(Constant.SCOPE_SCOPES, object.getClass());
            if (field != null) {
                try {
                    field.setAccessible(true);
                    field.set(object, scopes);
                    field.setAccessible(false);
                } catch (IllegalAccessException e) {
                    try {
                        field.set(object, null);
                    } catch (IllegalAccessException e1) {
                    }
                }
            }
            return;
        } else {
            //参数 是 权限范围字段
            if (parameter.getName().equals(Constant.SCOPE_SCOPES)) {
                parameter.setValue(scopes);
            }
        }
    }

    /**
     * 获得Web实例
     *
     * @param name  名字
     * @param clazz 类型
     * @return
     */
    public Object getWebInstance(String name, Class<?> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }

    /**
     * 获得Web实例
     *
     * @param clazz
     * @return
     */
    public Object getWebInstance(Class<?> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    /**
     * 获得Web实例
     *
     * @param name
     * @return
     */
    public Object getWebInstance(String name) {
        return getApplicationContext().getBean(name);
    }

    public HttpServletRequest getRequest() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getRequest();
        return request;
    }

    public HttpServletResponse getResponse() {
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        return response;
    }

}
